/*
 Menge Crowd Simulation Framework

 Copyright and trademark 2012-17 University of North Carolina at Chapel Hill

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0
 or
    LICENSE.txt in the root of the Menge repository.

 Any questions or comments should be sent to the authors menge@cs.unc.edu

 <http://gamma.cs.unc.edu/Menge/>
*/

/*!
 @file    GLViewer.h
 @brief   Specification for an OpenGL based 3D viewer.

 Can draw a SceneGraph GLScene, perform view manipulation and overlay text on the scene.
 */

#ifndef __GLVIEWER_H__
#define __GLVIEWER_H__

#include <string>
#include <vector>
#include "thirdParty/SDL/SDL.h"

#include "MengeVis/SceneGraph/GLCamera.h"
#include "MengeVis/SceneGraph/GLGroundPlane.h"
#include "MengeVis/SceneGraph/GLScene.h"
#include "MengeVis/Viewer/Profiler.h"
#include "MengeVis/Viewer/ScreenGrab.h"
#include "MengeVis/VisConfig.h"

namespace MengeVis {

// Forward declarations
class Image;

/*!
 @namespace   MengeVis::Viewer
 @brief       The namespace containing visualization functionality for the pedestrian simulation.
 */
namespace Viewer {

// Forward declarations
class ViewConfig;
class Watermark;

/*!
 @brief   Class for defining an OpenGL Viewer.
 */
class MENGEVIS_API GLViewer {
 public:
  /*!
   @brief   Default constructor based on preferred viewer size.

   @param   width     The width of the view window, in pixels.
   @param   height    The height of the view window, in pixels.
   */
  GLViewer(int width, int height);

  /*!
   @brief   Constructor based on configuration in a ViewConfig instance.

   @param   cfg   A fully specified view configuration.
   */
  GLViewer(ViewConfig& cfg);

  /*!
   @brief   Destructor.
   */
  ~GLViewer(void);

  /*!
   @brief   Runs the main loop -- including advancing the scene in time.
   
   Exiting this function means the main event loop is no longer running.
   */
  void run();

  /*!
   @brief   Sets the optional background image to the viewer.

   @param   imgName   A valid path to a valid image to load.
   @returns A boolean reporting success (true) or failure (false).
   */
  bool setBGImage(const std::string& imgName);

  /*!
   @brief   Reports if the viewer has a background image.

   @returns True if there is a background image, false otherwise.
   */
  bool hasBGImage() const { return _bgImg != 0x0; }

  /*!
   @brief   Sets the watermark to the viewer.

   @param   imgName   A valid path to a valid image to load.
   @returns A boolean reporting success (true) or failure (false).
   */
  bool setWatermark(const std::string& imgName);

  /*!
   @brief   Reports if the viewer has a watermark.

   @returns   True if there is a watermark, false otherwise.
   */
  bool hasWatermark() const { return _waterMark != 0x0; }

  /*!
   @brief   Clears the background image from the viewer.
   */
  void clearBGImage();

  /*!
   @brief   Initializes the viewer with the given title.

   @param   title   The name displayed at the top of the window.
   @returns True if initialization was successful, false otherwise.
   */
  bool initViewer(const std::string& title);

  /*!
   @brief   Resizes the window to the indicated size.

   @param   w   The new width of the viewer window.
   @param   h   The new height of the viewer window.
   */
  void resizeGL(int w, int h);

  /*!
   @brief   Initializes the opengl mechanism.
   */
  void initGL();

  /*!
   @brief   Sets the GLScene to be drawn.

   A viewer can only draw a single scene; all drawn elements belong either to the GLScene or a
   GLContext.

   @param   scene   The GLScene to be rendered and temporally advanced.
   */
  void setScene(SceneGraph::GLScene* scene);

  /*!
   @brief   Causes the viewer to recognize that a new OpenGL context has been created (such as
            window resizes).
   */
  void newGLContext();

  /*!
   @brief   Main drawing function -- draws scene and context as available.
   */
  void drawGL();

  /*!
   @brief   Processes a mouse event.

   @param   e   An SDL_Event corresponding with an appropriate SDL mouse event (button up/down,
                move).
   @returns  A boolean indicating if a scene redraw is necessary (true) or not (false).
   */
  bool handleMouse(SDL_Event& e);

  /*!
   @brief    Processes a keyboard event.

   @param   e   An SDL_Event corresponding with an appropriate SDL keyboard event (key up/down).
   @returns A boolean indicating if a scene redraw is necessary (true) or not (false).
   */
  bool handleKeyboard(SDL_Event& e);

  /*!
   @brief   Sets whether the world axis is drawn or not.

   @param   state   A boolean controlling whether the world axis is drawn (true) or not (false).
   */
  void setDrawWorldAxis(bool state) { _drawWorldAxis = state; }

  /*!
   @brief   Reports the state of whether the world axis is drawn or not.

   @returns A boolean indicating if the world axis is drawn (true) or not (false).
   */
  bool getDrawWorldAxis() const { return _drawWorldAxis; }

  /*!
   @brief   Draws a red frame around the viewer, indicating that images are being saved.
   */
  void drawRecordingFrame() const;

  /*!
   @brief   Sets the horizontal field of view for the ith camera.

   @param   i     The index of the camera - index value is NOT checked.
   @param   fov   The horizontal field of view for the indicated camera. The value is not validated.
   */
  void setCameraFOV(int i, float fov) { _cameras[i].setFOV(fov); }

  /*!
   @brief   Sets the distance to the camera's far clipping plane.

   @param   i       The index of the camera - index value is NOT checked.
   @param   dist    The distance, from the camera, to its far clipping plane. The value is not
                    validated.
   */
  void setCameraFarPlane(int i, float dist) { _cameras[i].setFarPlane(dist); }

  /*!
   @brief   Change the scene time by the given offset.

   @param   delta   The amount of time change. Can be negative, but only meaningful if the GLScene
                    can support negative time changes.
   */
  void offsetTime(float delta);
  /*!
   @brief   Sets the current time.

   @param   t   The current time for the viewer and its GLScene.
   */
  void setTime(float t) { _viewTime = t; };

  /*!
   @brief   Sets the view's background color.

   This must be called AFTER the view has been initialized.

   @param   r   The red component of the background color (must be in the range [0,1]).
   @param   g   The green component of the background color (must be in the range [0,1]).
   @param   b   The blue component of the background color (must be in the range [0,1]).
   */
  void setBGColor(float r, float g, float b);

  /*!
   @brief   Gets a pointer to the ith camera.

   @param   i   The index of the desired camera. The index value is not checked.
   @returns A pointer to the ith camera.
   */
  SceneGraph::GLCamera* getCamera(int i = 0) { return &_cameras[i]; }

  /*!
   @brief   Sets the simulator to use a fixed time step, with the given value.

   @param   stepSize    The size of the fixed step the viewer should advance its GLScene.
   */
  void setFixedStep(float stepSize);

  /*!
   @brief   Sets the path for when outputting screen capture images.

   @param   path    The path to which output images should be written.
   */
  void setDumpPath(const std::string& path);

 protected:
  /*!
   @brief   The various SDL flags which determine the view.
   */
  static Uint32 FLAGS;

  ////////////////////////////////////////////////////////////////
  // members

  /*!
   @brief   The SDL window for this view.
   */
  SDL_Window* _window;

  /*!
   @brief   The width of the viewer window (in pixels).
   */
  int _width;

  /*!
   @brief   The height of the viewer window (in pixels).
   */
  int _height;

  /*!
   @brief   The GLScene to draw.
   */
  SceneGraph::GLScene* _scene;

  /*!
   @brief   A set of cameras from which to draw the scene.
   */
  std::vector<SceneGraph::GLCamera> _cameras;

  /*!
   @brief   The index of camera currently being used to draw the scene.
   */
  size_t _currCam;

  /*!
   @brief   The x-position of the mouse when a mouse button was depressed (in screen space).
   */
  int _downX;

  /*!
   @brief   The y-position of the mouse when a mouse button was depressed (in screen space).
   */
  int _downY;

  // HUD/UI components

  /*!
   @brief   Determines if the GLScene needs to update.
   */
  bool _update;

  /*!
   @brief   Determines whether a world-aligned axis is drawn.
   */
  bool _drawWorldAxis;

  /*!
   @brief   Determines whether the frame rate is displayed in the viewer.
   */
  bool _showFPS;

  /*!
   @brief   A timer to determine the displayed frame rate value.
   */
  SampleTimer _fpsDisplayTimer;

  /*!
   @brief   Controls whether the viewer advances the GLScene (true) or not (false).
   */
  bool _pause;

  /*!
   @brief   Determines if the viewer should still operate -- as long as it is true, it will continue
            its main loop (@see GLViewer::run).
   */
  bool _running;

  /*!
   @brief   The current time at which the viewer is running.  Modified by calls to 
            GLViewer::setTime() and GLViewer::offsetTime().
   */
  float _viewTime;

  /*!
   @brief   The viewer's background color.
   */
  Menge::Math::Vector3 _bgColor;

  /*!
   @brief   The step size for fixed-step simulation.
   */
  float _stepSize;

  /*!
   @brief   A pointer to the optional background image.
   */
  Image* _bgImg;

  /*!
   @brief   An optional watermark.
   */
  Watermark* _waterMark;

  /*!
   @brief   Determines if screen capture images will be output at each time advance.
   */
  bool _dumpImages;

  /*!
   @brief   Reports if the screen capture output path is valid.
   */
  bool _validDumpPath;

  /*!
   @brief   The path to the folder in which to write screen capture images.
   */
  std::string _dumpPath;

  /*!
   @brief   The number of images which have been output.
   
   Used for labeling the image file names into a sequential order.
   */
  size_t _imgCount;

  /*!
   @brief   The set of lights to use in rendering.
   */
  std::vector<SceneGraph::GLLight> _lights;

  /*!
   @brief   Initizlies the OpenGL lighting based on the set of lights.
   */
  void initLighting();

  ////////////////////////////////////////////////////////////////
  // methods

  /*!
   @brief   Initializes the SDL mechanism for event handling and context creation.
   */
  bool initSDL(const std::string& title);

  /*!
   @brief   Draws a simple, three-color world axis at the origin of world space.
   */
  void drawWorldAxis() const;
};
}  // namespace Viewer
}  // namespace MengeVis
#endif  // __GLVIEWER_H__
